#include "translator.hpp"
#include "rose.h"
#include <iostream>
#include <vector>
#include <string>
#include <stack>
#include <assert.h>
#include <sstream>

using std::cout;
using std::endl;
using std::vector;
using std::string;
using std::stack;
using std::stringstream;

using namespace SageBuilder;
using namespace SageInterface;

///_________________________________________________________________________________________________
///

/*
    TODO: handle arrays, and intend attributes.
          intend([in]out) should be converted to a pointer type
*/
SgType* ftc::xf_type(SgType* type) {
    assert(type!=NULL);

    //temporary, don't handle arrays yet!
    assert(!type->containsInternalTypes());
    
    if(isSgTypeVoid(type)) return buildVoidType();
    if(isSgTypeBool(type)) return buildIntType(); //NOTE bool->int
    if(isSgTypeInt(type)) {
        auto* itype = isSgTypeInt(type);
        auto* kind = itype->get_type_kind();
        if(kind!=NULL) {
            if(isSgIntVal(kind)) {
                int value = isSgIntVal(kind)->get_value();
                switch(value) {
                    case  2: return buildShortType();
                    case  4: return buildIntType  ();
                    case  8: return buildLongType ();
                    case 16: return buildLongLongType();
                    default:
                        throw "Integer type with size <> {2,4,8,16}?";
                }
            }else throw "Integer type with non integral kind?";
        }else return buildIntType();
    }
    if(isSgTypeFloat(type)) {
        auto* ftype = isSgTypeFloat(type);
        auto* kind = ftype->get_type_kind();
        if(kind!=NULL) {
            if(isSgIntVal(kind)) {
                int value = isSgIntVal(kind)->get_value();
                switch(value) {
                    case  4: return buildFloatType();
                    case  8: return buildDoubleType();
                    case 16: return buildLongDoubleType();
                    default:
                        throw "Floating type with size <> {4,8,16}";
                }
            }else throw "Floating type with non integral kind?";
        }else return buildFloatType();
    }
    
    throw "Unhandled SgType in tcf::xf_type :: SgType* -> SgType*";
    return NULL;
}

///_________________________________________________________________________________________________
///

namespace ftc {
    
    SgValueExp* xf_value_exp(SgValueExp* val) {
        assert(val!=NULL);
        
        if(isSgBoolValExp(val))
            return buildIntVal(isSgBoolValExp(val)->get_value()); //NOTE bool->int
        
        #define DEF(T) if(isSg##T##Val(val)) \
            return build##T##Val(isSg##T##Val(val)->get_value());
        
        DEF(Int)
        DEF(Short)
        DEF(LongInt)
        DEF(LongLongInt)
        
        DEF(Float)
        DEF(Double)
        DEF(LongDouble)
        
        #undef DEF
    
        throw "Unhandled Value type in tcf::xf_expr::xf_value_exp :: SgValueExp* -> SgValueExp*";
        return NULL;
    }
    
    ///_____________________________________________________________________________________________
    ///

    SgUnaryOp* xf_unop_exp(SgUnaryOp* unop) {
        assert(unop!=NULL);
        
        auto* nexp = ftc::xf_expr(unop->get_operand());
        
        SgUnaryOp* ret = NULL;
        
        #define DEF(T) else if(isSg##T##Op(unop)) \
            ret = build##T##Op(nexp);
        
        if(false) {}
        DEF(MinusMinus)
        DEF(PlusPlus)
        DEF(Minus)
        DEF(Not)
        DEF(BitComplement)
        
        #undef DEF
        
        if(ret==NULL)
            throw "Unhandled UnaryOp type in tcf::xf_expr::xf_unop_exp :: SgUnaryOp* -> SgUnaryOp*";
        else {
            //set postfix/prefix mode.
            ret->set_mode(unop->get_mode());
            return ret;
        }
    }
    
    ///_____________________________________________________________________________________________
    ///
    
    SgBinaryOp* xf_binop_exp(SgBinaryOp* binop) {
        assert(binop!=NULL);
        
        auto* lexp = ftc::xf_expr(binop->get_lhs_operand());
        auto* rexp = ftc::xf_expr(binop->get_rhs_operand());
        
        SgBinaryOp* ret = NULL;
        
        #define DEF(T) if(isSg##T##Op(binop)) \
            ret = build##T##Op(lexp,rexp);
            
        if(false) {}
        DEF(Add)
        DEF(AndAssign)
        DEF(And)
        DEF(Assign)
        DEF(BitAnd)
        DEF(BitOr)
        DEF(BitXor)
        DEF(DivAssign)
        DEF(Divide)
        DEF(Equality)
        DEF(GreaterOrEqual)
        DEF(GreaterThan)
        DEF(LessOrEqual)
        DEF(LessThan)
        DEF(MinusAssign)
        DEF(MultAssign)
        DEF(Or)
        DEF(Multiply)
        DEF(NotEqual)
        DEF(PlusAssign)
        DEF(Subtract)
        DEF(XorAssign)
   
        else if(isSgExponentiationOp(binop)) {} //FORTRAN operation.
            
        #undef DEF
        
        if(ret==NULL)
            throw "Unhandled BinaryOp type in tcf::xf_expr::xf_binop_exp :: SgBinaryOp* -> SgBinaryOp*";
        else
            return ret;
    }
}

///_________________________________________________________________________________________________
///

SgExpression* ftc::xf_expr(SgExpression* expr) {
    assert(expr!=NULL);
    
    if(isSgVarRefExp(expr)) {
        auto* ref = isSgVarRefExp(expr);
        return buildVarRefExp(ref->get_symbol()->get_name());
    }
    
    if(isSgValueExp(expr)) {
        auto* val = isSgValueExp(expr);
        return xf_value_exp(val);
    }
    
    if(isSgUnaryOp(expr)) {
        auto* unop = isSgUnaryOp(expr);
        return xf_unop_exp(unop);
    }
    
    if(isSgBinaryOp(expr)) {
        auto* binop = isSgBinaryOp(expr);
        return xf_binop_exp(binop);
    }
    
    throw "Unhandled SgExpression in tcf::xf_expr :: SgExpression* -> SgExpression*";
    return NULL;
}

///_________________________________________________________________________________________________
///

namespace ftc {

    class GFinder : public AstSimpleProcessing {
        SgGlobal* ret;
    public:
        void visit(SgNode* n) {
            if(ret==NULL) ret = isSgGlobal(n); //may remain NULL if n cannot be coerced.
        }
        SgGlobal* find(SgNode* root) {
            ret = NULL;
            traverse(root,preorder);
            return ret;
        }
    };
    
    //used (FOR THE MOMENT!) to generate unique FOR loop variables.
    int FOR_VARIABLE_NAME = 0;
    string genForVariableName() {
        stringstream ret;
        ret << "__FOR_VAR_";
        ret << FOR_VARIABLE_NAME;
        FOR_VARIABLE_NAME++;
        return ret.str();
    }

    class AstVisitor : public AstPrePostProcessing {
        //used when I want to skip the traversal of an entire sub-tree.
        //keep ignoring nodes until postOrderVisit is called with the sync node.
        SgNode* sync;
        
    public:
        //used to ignore parameter declarations in procedure bodies
        vector<SgInitializedName*>* arguments;
    
        AstVisitor() {
            sync = NULL;
            arguments = NULL;
        }

        void preOrderVisit(SgNode* n);
        void postOrderVisit(SgNode* n);
    };
}

///_________________________________________________________________________________________________
///

namespace ftc {

    void xf_block(SgBasicBlock* fort_block, SgBasicBlock* c_block, vector<SgInitializedName*>* arguments) {
        pushScopeStack(c_block);
        
        //traverse block;
        AstVisitor visitor;
        
        //ignore any declaration of function arguments in the body.
        visitor.arguments = arguments;
        visitor.traverse(fort_block);
        
        popScopeStack();
    }

    SgFunctionDeclaration* xf_fn_decl(SgFunctionDeclaration* decl) {
        assert(decl!=NULL);
        
        SgName fn_name = decl->get_name();
        auto* fn_args = buildFunctionParameterList();
        auto args = decl->get_args();
        for(auto i = args.begin(); i!=args.end(); i++) {
            auto* init_name = *i;
            SgName arg_name = init_name->get_name();
            SgType* arg_type = ftc::xf_type(init_name->get_type());
            
            appendArg(fn_args, buildInitializedName(arg_name, arg_type));
        }
        
        SgType* fn_return = ftc::xf_type(decl->get_type()->get_return_type());
        
        auto* fn_decl = buildDefiningFunctionDeclaration(fn_name, fn_return, fn_args);
        auto* fn_defn = fn_decl->get_definition();
        auto* fn_body = fn_defn->get_body();
        
        auto* body = decl->get_definition()->get_body();
        xf_block(body, fn_body, &fn_decl->get_args());
        
        return fn_decl;
    }   
    
    //---------------------------------------------------------------------------------------------- 
    
    //arguments list so as to ignore declarations of those arguments in a procedure body.
    vector<SgVariableDeclaration*> xf_var_decl(SgVariableDeclaration* decl, vector<SgInitializedName*>* arguments) {
        assert(decl!=NULL);
        
        vector<SgVariableDeclaration*> ret;
        
        auto vars = decl->get_variables();
        for(auto i = vars.begin(); i != vars.end(); i++) {
            auto* init_name = *i;
            SgName var_name = init_name->get_name();
            
            //determine this variable is not a procedure argument to be ignored!
            if(arguments!=NULL) {
                bool found = false;
                
                for(auto j = arguments->begin(); j != arguments->end(); j++) {
                    if(var_name.getString().compare((*j)->get_name().getString())==0) {
                        found = true;
                        break;
                    }
                }
                
                if(found)
                    continue;
            }
            
            //onwards!
            SgType* var_type = ftc::xf_type(init_name->get_type());
            auto* var_decl = buildVariableDeclaration(var_name,var_type);
            ret.push_back(var_decl);
        }
        
        return ret;
    }
    
    //----------------------------------------------------------------------------------------------
    
    SgForStatement* xf_fortran_do(SgFortranDo* fdo) {
        assert(fdo!=NULL);
        
        auto* do_init = fdo->get_initialization();
        if(!isSgAssignOp(do_init)) {
            throw "Unhandled FortranDo Initialisation expression in ftc::xf_fortran_do";
            return NULL;
        }
        
        auto* for_init_exp = isSgAssignOp(ftc::xf_expr(do_init));
        auto* counter = for_init_exp->get_lhs_operand();
        if(!isSgVarRefExp(counter)) {
            throw "Unhandled FortranDo Initialisation 2; assignOp LHS was not a variable";
            return NULL;
        }
        auto* for_init = buildExprStatement(for_init_exp);
        
        //
        
        auto* do_bound = fdo->get_bound();
        string bound_name = genForVariableName();
        SgType* bound_type = ftc::xf_type(do_bound->get_type());
        auto* bound_decl = buildVariableDeclaration(SgName(bound_name), bound_type);
        appendStatement(bound_decl);
        
        auto* for_test_exp = ftc::xf_expr(do_bound);
        appendStatement(buildExprStatement(buildAssignOp(buildVarRefExp(SgName(bound_name)), for_test_exp)));
        
        auto* for_test = buildExprStatement(buildLessOrEqualOp(counter, buildVarRefExp(SgName(bound_name))));
        
        //
        
        auto* do_step = fdo->get_increment();
        string step_name = genForVariableName();
        SgType* step_type = ftc::xf_type(do_bound->get_type());
        auto* step_decl = buildVariableDeclaration(SgName(step_name), step_type);
        appendStatement(step_decl);
        
        auto* for_step_exp = ftc::xf_expr(do_step);
        appendStatement(buildExprStatement(buildAssignOp(buildVarRefExp(SgName(step_name)), for_step_exp)));
        
        auto* for_step = buildPlusAssignOp(counter, buildVarRefExp(SgName(step_name)));
        
        //
        
        auto* body = buildBasicBlock();
        auto* ret = buildForStatement(for_init,for_test,for_step,body);
        xf_block(fdo->get_body(), body, NULL);
        
        return ret;
    }
}

void ftc::AstVisitor::preOrderVisit(SgNode* n) {
    if(sync!=NULL) return;
    if(isSgGlobal(n)) return;
            
    cout << "VISIT: " << n->sage_class_name() << endl;
           
    if(isSgProcedureHeaderStatement(n)) {
        auto* proc = xf_fn_decl(isSgFunctionDeclaration(n));
        appendStatement(proc);
        sync = n;
        return;
    }
    
    if(isSgVariableDeclaration(n)) {
        auto vdecls = xf_var_decl(isSgVariableDeclaration(n), arguments);
        for(auto i = vdecls.begin(); i!=vdecls.end(); i++)
            appendStatement(*i);
        sync = n;
        return;
    }
    
    if(isSgExprStatement(n)) {
        auto* expr = ftc::xf_expr(isSgExprStatement(n)->get_expression());
        appendStatement(buildExprStatement(expr));
        sync = n;
        return;
    }
    
    if(isSgFortranDo(n)) {
        auto* state = ftc::xf_fortran_do(isSgFortranDo(n));
        appendStatement(state);
        sync = n;
        return;
    }
}
        
void ftc::AstVisitor::postOrderVisit(SgNode* n) {
    if(isSgGlobal(n)) return;
    if(n==sync) sync = NULL;
}

///_________________________________________________________________________________________________
///

SgFile* ftc::xf_file(SgFile* file, SgProject** project) {
    assert(project!=NULL);
    
    if(file->get_outputLanguage()!=SgFile::e_Fortran_output_language) {
        throw "Input file was not a Fortran file in tcf::xf_file :: SgFile* -> SgProject** -> SgFile*";
        return NULL;
    }
    
    //output file name: eg.f90 -> eg.c
    string fname = file->getFileName();
    fname.erase(fname.find_last_of('.'));
    fname.append(".c");
    
    //create new file, redirect for C unparsing
    SgFile* outf = buildFile("dummy.f90",fname,*project);
    outf->set_outputLanguage(SgFile::e_C_output_language);
    
    if(*project==NULL) *project = outf->get_project();
    
    //find SgGlobal for current and new file
    GFinder gfinder;
    SgGlobal* ngscope = gfinder.find(outf);
    SgGlobal* pgscope = gfinder.find(file);

    //push global scope for new file    
    pushScopeStack(ngscope);
    
    //traverse AST of old file starting from global scope, and translate away!
    AstVisitor visitor;
    visitor.traverse(pgscope);
    
    popScopeStack();

    return outf;
}

